Implement a Skip Link Component
On this page
There is repeated content in the header of every page of the CampSpots app.
When a keyboard user navigates, they will have to Tab through the menu in order to move down to the main content of the page. Of course, this isn’t as bad as having to Tab through all of the non-visible menu items like we saw previously but it still doesn’t provide a great experience.
Skip links to the rescue!
Not only are skip links useful for keyboard access, they are also a requirement in WCAG Success Criterion 2.4.1, Bypass Blocks (Level A).
They are commonly used to “Skip to Main Content” to bypass repeated navigation and occasionally for “Back to Top” on a long page. They can also be a useful pattern for non-modal dialogs or other designs where it would be nice to jump around the page by keyboard.
A skip link itself can point to an ID attribute on a container element, such as <a href="#main"> pointing to <main id="main">.
Skip links should be visible to all keyboard users, i.e. not visually-hidden for screen reader use only as was once commonplace in the past.
🛠 Challenge: Add a Skip Link
Your challenge is to add a skip link to the CampSpots app that allows a keyboard user to skip past the header and straight to the main content of the page.
Looking at our App.js file, we can get an idea of the structure of the CampSpots application.
There’s a <Header/> component that will be at the top no matter what. This makes it a good candidate for where the skip link component should live.
Below the Header is a main landmark with an id of main, which gives us a target for our internal skip link.
A SkipLinks component has already been started at components/skip-links.js. It’s got a basic style sheet already created and is ready for you to write markup. Add the SkipLinks component to the Header.
The skip link should point to the main landmark and should not be visible until the user hits the Tab key. Hint: Revisit the visibility techniques from the previous lesson!
If you want to peek at the answer, check out the files in the exercise1-the-keyboard directory of the repo.
Video Transcript
On our webpage. We have some repeated content like we've got this header Really, if, you know, if I got tired of having to go through this header, at least it's not forcing me through all of the items and the sub menu.
But it would be nice if we could skip by or bypass that content in the header. So for our first exercise, we're going to implement a skip links component that is helpful on all kinds of websites and web applications. And the goal is to have something that's rendered that only shows up when we hit tab key.
So it should be the first thing in the tab order of the page. When we hit tab, it'll make this component visible and it'll have a link, or it can have multiple links in it to help us jump around with the keyboard to various parts of the webpage. So we can use internal links to link, to say the main content or the footer.
And if you have a really long webpage, having these skip links can be really nice ergonomics, you can use similar techniques for things like back to top. If you've got a really long page and you want to give keyboard users a way to jump back to the top, it's kind of a similar concept.
So to implement our skip links component, I'm going to come over to vs code.
And it seems like in our header, that could be a good spot to implement a component. We've also seen. Our app component sort of in the root of the project. So in here we've got, we've got a main element or main landmark. It has an idea of main. We also have a footer id of footer. So those are candidates for being the targets of our skip links.
So I think we could start with doing the one for main ID main have a link internal link that pops up in the header and the first thing in the tab order. And we'll have it like help us jump down to the main content and bypass the header. So that is our first exercise and something I will point out is in our exercise.
One directory, we've got to read me in here that you can refer back to if you like talks about skip links and various things. And there's also a note here about which success criterion we're applying so I'm going to open the preview here. So skip links are a level, a requirement of the web content accessibility guidelines.
And so this is a way to help keyboard and screen reader users skip past really repetitive content. So if you've got a giant mega. That's a bunch of stuff in it. Or just repeated sections of pages. I've been auditing a project that has a long sidebar with a bunch of stuff in it, and that stuff is great.
But when you're testing with the keyboard or using the keyboard gets really annoying having to tab past every single item in this long sidebar. So a bypass block or skip link would be amazing. So that's what we're going to implement for this. So we've seen the header component. That's what we're going to instantiate our skip link.
We have a starter file here in our components directory for skip links. It's kind of a shell of a component. So it's a functional component that we can import into various other places, including our app file. It's got a style sheet already. So in our component styles directory, we have a skip links dot SCSS file.
So this has some of the style for having a skip links component and any, so it's an unordered list that we're going to put these things into in case we have more than one link. And then the within that unordered list, it will have anchors. Those will be focusable and they will have href values that are internal links that jump us to the matching ID.
So. Idea of main. We can supply that as an a ahref for an internal link, and that will give us some functionality.
🛠 Solution: Adding a Skip Link
Implementing the SkipLinks Component
For the SkipLinks component, it would be good to have it support more than one link in case there are multiple places we want the user to reach.
When I know the component I’m creating needs to be versatile, I like to have it take in child elements.
With this in mind, I’m going to return an unordered list. It will render the children that get passed in: one or more <li>s with appropriate links inside. The children prop can be destructured directly from the component parameters.
Since the baseline CSS has already been written, I’ll add the className of skip-links to the <ul>.
Here’s what my code for the SkipLinks component looks like:
const SkipLinks = ({children}) => {
return (
<ul className="skip-links">
{children}
</ul>
)
}Video Transcript
All right. So let's go in here and do some magic, make a skip links component. So if we're going to make something that's versatile we want an item that can take children. So that we can import this component and have it pull in styles and things.
So we don't have to write them every time, but maybe we want it to be able to render more than one link. Like if we had a link in, two links in the header you know, maybe one that jumped us to main and one that jumped us to footer, having this component be flexible. So it could take both would be kind of nice.
So let's say this skip links component renders and unordered list, and we're going to make it match some of the styles in our skiplinks.scss file. So that files got a class of skip-links. So we could say skip links. If you wanted to use something like CSS and JS or CSS modules here, you could, I'm a bit old school.
I like using regular old CSS or sass in that case. So we're applying that CSS class name of skip links. And then if I wanted to make this ultimately flexible to take multiple children or multiple links, I'm just gonna use a children prop that this component will take in and it will match. So if I pass any child elements in, when we call this component, it will apply them down here as children of the UL.
So I'm gonna hit save, we'll get our working version of this component. Since it already had styles, kind of sped up the process here for us, makes it pretty short and easy to come and implement this component. So we've got the shell of something that can take children. And it's got the styles here, some of the styles I should say.
So let's go instantiate this and see what we're working with
Adding the SkipLink Component to the Header
Over in the Header component at components/header.js, the SkipLinks component needs to be imported:
import SkipLinks from "components/skip-links"We want our skip link to show before anything else, so we’ll add it above the div with the id of header-nav.
Because the SkipLink's markup takes in children and React’s JSX also supports regular HTML elements, we can put the li elements with links in between <SkipLink> opening and closing tags to be rendered.
Here’s what it looks like:
const Header = () => {
return (
<header id="header">
<SkipLinks>
<li><a href="#main">Skip to Main Content</a></li>
</SkipLinks>
<div id="header-nav">
...When we go back to the browser and refresh, our new skip link component is displayed!
However, we don’t want the links to display until the user hits the Tab key.
You may need to hold the Shift key to do a hard refresh in Chrome thanks to caching.
Video Transcript
In our header. I need to import this component. So I will say import skip links from, and we've got an alias to our components directory, so I can say components. Skip links. I don't need the extension because of JavaScript magic. So we've got that pulled in now inside of our header, I can say skip links and pull in this component.
So part of the magic with react and JSX, that's this templating language here is that I can use components and HTML seamlessly. So that's why some things are elements you may have seen before, like header and div and anchor. And that's enables me in JSX. I can also pull in custom things like skip links.
So our component, the only proper option that it took in was children. So anything that I include inside of here such as a list item, and I could say, so a single list item to start, I'll put an anchor in here and say, skip to main content. And we wanted it to match that id of main. So I could say hash main to do an internal link.
And, you know, I could make this component more sophisticated, maybe give it some more guardrails so that you can't put the wrong markup inside of a UL. But for this, I'm just gonna let it take in children, whatever I put in there. So in this case, we need an li so that it's a well-structured unordered list.
And then inside of that li, we'll put this anchor tag to give us an internal link. And if I hit save, I'm gonna come back to the browser, I am going to inspect and make sure that this worked, make sure we got in here. I think, yes, I needed to hit a shift refresh. So if it's not showing up hit shift, refresh that I'll get it to pull this in correctly.
So now our default, so it's, it's rendering it, it's there, but we're showing it for everybody, which we might not want to.
Update CSS so the Skip Link is Hidden by Default
The SkipLinks component is first in the tab order which is the desired behavior. But it should only be displayed when it’s focused.
The CSS needs to be updated to implement the hiding behavior, so I’ll open up skip-links.scss to add some rules.
There are a few options for which technique to choose.
I think that the skip link should be rendered in the accessibility tree so that it will be readable and functional to screen readers regardless if it can be visually seen on the screen.
I’m going to use the opacity technique, since it’s not shown visually but is still rendered.
Inside the style declarations for the li, I’ll add opacity: 0; which will hide it visually by default.
li a {
background-color: var(--color-accent-secondary);
color: var(--color-primary-light);
font-weight: bold;
opacity: 0;
...When we refresh the page, we can no longer see our skip links even though the Accessibility Tree shows it being rendered:
Now we need to fix it so that a skip link appears when we Tab to it.
Back in skip-links.scss, I’ll add a rule for the focus pseudo-class that will set the opacity value to 1 when the link has focus. Because this project uses Sass, I can save some typing for the focus rule by using &:focus.
Here’s what the link styles look like now:
li a {
background-color: var(--color-accent-secondary);
color: var(--color-primary-light);
font-weight: bold;
opacity: 0;
padding: 0.5em;
position: absolute;
text-decoration: none;
z-index: 1;
&:focus {
opacity: 1;
}
}Back in the browser, the skip link is shown when it has focus and is visually hidden when it doesn’t!
Further Consideration
There are some considerations to make for both the visual style of the skip link and its placement, particularly when looking at smaller viewport sizes. However, functionally we have something that works and suits our purposes for now.
Video Transcript
So we need to add a little bit of CSS to make this interactive instead of showing by default. Cause really the goal of this is to make it the first item in the tab order. So we've got that, but we want it to display when we focus on it instead of showing by default.
So it's kind of a conditionally shown component using CSS coming back over here to vs code. I'm going to go over to our style sheet and we'll add some of the style for this. So we've seen our hiding techniques. We've got a few to choose from. So we do really have options because well, the UL itself has a position relative.
But the li anchor has a position of absolute. So it's kind of pulled out of the flow of the document so we could use visually hidden. We could use opacity visibility hidden. So I guess thinking through that a little bit, we want it to not be shown visibly. So we've got a number of options we've seen so far that could work.
I guess the question about how far we should go is, do we want it to be rendered to assistive technology to start off? I tend to think, yes, we should choose a hiding technique that will render this in a screen reader. So that it'll show up when we hit tab. But if a screen reader, user say you fire up a screen reader and you pull up either the voiceover rotor or NVDA's elements list if we let this be rendered in the accessibility tree, the link will be part of the list it'll be available and it'll be functional.
Even if it's not shown visually, it's still there in the list and the screen reader. So I'm going to choose opacity so it's not shown visually, but it's still rendered. And I can put that in here as kind of the default state for the anchor inside of the li so the UL, because it doesn't have a list style, it won't, you won't see anything until this anchor becomes visible.
So if I hit save now, We've got a list of skip links in here, but they have no dimensions. So that's cool. That means that we've got the kind of visual display we want. But we don't see things just on page load. We do have a tab stop in here that we need to resolve. So this anchor, we want to become visible when we focus on it.
So let's go add that. We'll come back over here to vs code and I can use some CSS to show something when I have a focus like when it matches the focus event. So inside of our anchor because I'm using Sass I can say, and colon focus and I can override that opacity and say opacity of one. So when it takes focus, change its opacity back to one and it will become visible.
So with that, I can come back in here. Ooh, look at that. So when I tab, I see the skip to main content. It's overlapping our logo link here. So if you wanted to play with the visual style, change the color. I chose kind of a maroon background with the white text so that it would stand out. I think I even sampled it from somewhere in this image, but we want it to stand out.
So it's obvious. I don't love the overlap over the logo text, but we can always move it. Something I would keep in mind is that on smaller viewport sizes? We want it to not get cut off, off to one side, but maybe I could move it up a little bit so that it's not overlapping. Yeah, we've got options, but functionally, we have something now that in the first tab stop takes focus, becomes visible.
And if I hit the enter key, it jumps me down the page. So it jumps me to that main internal link. So that matches up with an ID on the page. So cool. We've got, we've got one.
🛠 Challenge: Add a “Back to Top” Link to the Footer
Following a similar process to what I did above, add an instance of the SkipLinks component to the footer that allows the user to jump back to the top of the page.
📼 Solution: Adding “Back to Top” to the Footer
Inside of App.js, we need to import the SkipLinks component at the top of the file.
At the bottom of the file in the footer section, we’ll add an instance of the SkipLinks component with a child link and an href that points to the id of header. The text will say “Skip to Top”.
Here’s what the markup looks like:
// inside of App.js
<footer id="footer">
<SkipLinks variant="footer">
<li><a href="#header">Skip to Top</a></li>
</SkipLinks>
<div className="layout">
<div id="footer-logo">
<img src={imgFooterLogo} alt="CampSpots" />
</div>
</div>
</footer>We could take this further and update the SkipLinks component to allow for different style variants by applying CSS classes as strings.
Over in the skip-links.js file, we’ll add support for a variant prop to be passed in. The default will be set to “header”.
Then we’ll update the ul's className to use a string template and have it interpolate the value of the variant prop.
Here’s what the updated SkipLinks component looks like:
const SkipLinks = ({children, variant = 'header'}) => {
return (
<ul className={`skip-links ${variant}`}>
{children}
</ul>
)
}From there, customization would be a matter of adding styles for the variants in your stylesheet.
Wrapping Up Skip Links
There are multiple ways to build skip links but the functionality should be the same.
We want something that’s in the tab order that becomes visible to sighted keyboard users and screen reader users alike. A link should move the user’s focus point to another part of the page when activated.
Using our CSS visibility techniques allows us to render our skip links, make them visible on focus, and read them aloud in a screen reader.
📝 Exercise: Add Skip Links to Your Own Project
For more practice with Skip Links, add them to your own project!
Share a blog post about what visibility technique you used and how you decided where to add them in your code.